#pragma once

#include <hash_map>
#include <map>

class NxPhysicsSDK;
class NxControllerManager;
class BaseObject;

class ObjectIterator
{
	friend class Engine;

public:
	ObjectIterator() : m_Ptr(NULL) { }

	ObjectIterator & operator ++ ();
	ObjectIterator operator ++ (int) { ObjectIterator r = *this; operator++(); return r; }
	BaseObject & operator * () const { return *m_Ptr; }
	BaseObject * operator -> () const { return m_Ptr; }
	bool operator == (const ObjectIterator &it) const { return m_Ptr == it.m_Ptr; }
	bool operator != (const ObjectIterator &it) const { return m_Ptr != it.m_Ptr; }

private:
	BaseObject *m_Ptr;

	////// For class Engine
	ObjectIterator(BaseObject *Ptr) : m_Ptr(Ptr) { }
};

class Engine
{
	friend class BaseObject;
	friend class ObjectIterator;

public:
	Engine(bool usePhysxCooking);
	~Engine();

	// Call every frame.
	void OnUpdate();

	uint GetObjectCount() { return m_IdToObjectMap.size(); }

	ObjectIterator ObjectsBegin() { return ObjectIterator(m_FirstObject); }
	ObjectIterator ObjectsEnd() { return ObjectIterator(NULL); }
	ObjectIterator ObjectToIterator(BaseObject *obj) { return ObjectIterator(obj); }

	ObjectIterator FindObjectById(uint Id);
	ObjectIterator FindObjectByName(const string &Name);
	ObjectIterator MustFindObjectById(uint Id);
	ObjectIterator MustFindObjectByName(const string &Name);

	template <typename T> T * FindObjectById_T(uint Id);
	template <typename T> T * FindObjectByName_T(const string &Name);
	template <typename T> T * MustFindObjectById_T(uint Id);
	template <typename T> T * MustFindObjectByName_T(const string &Name);

	NxPhysicsSDK * GetPhysics() { return m_Physx; }
	NxControllerManager * GetPhysicsControllerManager() { return m_PhysxControllerManager; }
	bool IsPhysxCooking() { return m_UsePhysxCooking; }

private:
	BaseObject *m_FirstObject, *m_LastObject;
	BaseObject *m_FirstUpdateObject, *m_LastUpdateObject;
	uint m_NextObjectId;

	typedef stdext::hash_map<uint, BaseObject*> IdToObjectHashMap;
	typedef std::map<string, BaseObject*> NameToObjectMap;
	IdToObjectHashMap m_IdToObjectMap;
	NameToObjectMap m_NameToObjectMap;
	std::vector<BaseObject*> m_ObjectsForDeferredDelete;

	NxPhysicsSDK *m_Physx;
	bool m_UsePhysxCooking;
	NxControllerManager *m_PhysxControllerManager;

	void DeferredDelete();

	void CreatePhysics();
	void DestroyPhysics();

	////// For class BaseObject
	void RegisterObject(BaseObject *obj);
	void UnregisterObject(BaseObject *obj);
	void RegisterObjectName(BaseObject *obj);
	void AddObjectToUpdateList(BaseObject *obj);
	void RemoveObjectFromUpdateList(BaseObject *obj);
	void RegisterObjectForDeferredDelete(BaseObject *obj);
};

extern Engine *g_Engine;

template <typename T>
T * Engine::FindObjectById_T(uint Id)
{
	ObjectIterator it = FindObjectById(Id);
	if (it == ObjectsEnd()) return NULL;
	return dynamic_cast<T*>(&*it);
}

template <typename T>
T * Engine::FindObjectByName_T(const string &Name)
{
	ObjectIterator it = FindObjectByName(Name);
	if (it == ObjectsEnd()) return NULL;
	return dynamic_cast<T*>(&*it);
}

template <typename T>
T * Engine::MustFindObjectById_T(uint Id)
{
	T * r = FindObjectById_T<T>(Id);
	if (r == NULL) throw common::Error(common::Format("Cannot find object with id # as type \"#\"") % Id % typeid(T).name(), __FILE__, __LINE__);
	return r;
}

template <typename T>
T * Engine::MustFindObjectByName_T(const string &Name)
{
	T * r = FindObjectByName_T<T>(Name);
	if (r == NULL) throw common::Error(common::Format("Cannot find object with name \"#\" as type \"#\"") % Name % typeid(T).name(), __FILE__, __LINE__);
	return r;
}
